package codename.carrot.bomberman.network.client;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;

import android.util.Log;
import codename.carrot.bomberman.network.ConnectionThread;
import codename.carrot.bomberman.network.MessageHandler;
import codename.carrot.bomberman.network.MessageHandler.MessageType;
import codename.carrot.bomberman.network.server.ServerConnection;

/**
 * The connection thread for a client.
 * 
 * This class represent the connection. The server will have at most 3 of these in a connection pool, while a client will have at most 1.
 * 
 * This connection will send and recivie from the network.
 * 
 * @author Group A4.
 *
 */
public class ClientConnectionThread extends ConnectionThread {

	/**
	 * The connected socket.
	 */
	private Socket socket;
	/**
	 * The input stream of the socket.
	 */
	private InputStream input;
	/**
	 * The output stream of the socket
	 */
	private OutputStream output;
	/**
	 * The output stream for objects.
	 */
	private ObjectOutputStream oOutput;
	/**
	 * The input stream for sockets.
	 */
	private ObjectInputStream oInput;
	/**
	 * The ClientConnection. (Used only if we are connected as a client).
	 */
	private ClientConnection clientConnection = null;
	/**
	 * The ServerConnection. (Used only if we are connected as a server).
	 */
	private ServerConnection serverConnection = null;

	/**
	 * Constructs a clientConnectionThread for a connection between a client and the server. - USED BY CLIENTS.
	 * @param socket The connection socket.
	 * @param clientID The ID of the client. 
	 * @param clientConnection The ClientConnection.
	 * @param messageHandler The message handler.
	 */
	public ClientConnectionThread(Socket socket, int clientID, ClientConnection clientConnection, MessageHandler messageHandler) {
		super.clientID = clientID;
		super.messageHandler = messageHandler;
		this.socket = socket;
		this.clientConnection = clientConnection;
		try {
			input = socket.getInputStream();
			output = socket.getOutputStream();
			oOutput = new ObjectOutputStream(output);
			oInput = new ObjectInputStream(input);
			connected = true;
		} catch (IOException e) {
			Log.e(DEBUG, "IOException setting input/output stream", e);
		}
	}

	/**
	 * Constructs a clientConnectionThread for a connection between the server and a client. - USED BY THE SERVER.
	 * @param socket The connection socket.
	 * @param clientID The ID of the client. 
	 * @param serverConnection The serverConnection.
	 * @param messageHandler The message handler.
	 */
	public ClientConnectionThread(Socket socket, int clientID, ServerConnection serverConnection, MessageHandler messageHandler) {
		super.clientID = clientID;
		super.messageHandler = messageHandler;
		this.socket = socket;
		this.serverConnection = serverConnection;
		try {
			input = socket.getInputStream();
			output = socket.getOutputStream();
			oOutput = new ObjectOutputStream(output);
			oInput = new ObjectInputStream(input);
			connected = true;
		} catch (IOException e) {
			Log.e(DEBUG, "IOException setting input/output stream", e);
		}
	}

	@Override
	public synchronized boolean send(Object object) {
		try {
			oOutput.writeObject(object);
			oOutput.flush();
			oOutput.reset();
			return true;
		} catch (IOException e) {
			Log.e(DEBUG, "Fail to send", e);
			return false;
		} catch (Exception e) {
			Log.e(DEBUG, "Fail to send", e);
			terminate();
			return false;
		}
	}

	@Override
	public void terminate() {
		try {
			connected = false;
			socket.close();
			input.close();
			oInput.close();
			output.close();
			oOutput.close();
			interrupt();
			System.out.println("CONNECTION THREAD IS CLOSED");
		} catch (Exception e) {
			Log.d(DEBUG, "Socket close failed");
		}
	}

	/**
	 * Alert the main connection class that the connection that this class represents has ended.
	 */
	private void alertEndOfStream() {
		if (clientConnection != null) {
			Log.d(DEBUG, "END OF STREAM: Alerting CLIENT");
			clientConnection.terminate();
		}
		if (serverConnection != null) {
			Log.d(DEBUG, "END OF STREAM: Alerting SERVER");
			serverConnection.terminateClientConnection(this.getClientID());
		}
	}

	@Override
	public void run() {
		Log.i(DEBUG, "START Connection");

		while (connected) {
			try {
				Object object = oInput.readObject();
				if (object != null) {
					messageHandler.obtainMessage(MessageType.RECEIVE, clientID, object).sendToTarget();
				}
			} catch (SocketException e){
				Log.e(DEBUG, "End of stream", e);
				alertEndOfStream();
			} catch (EOFException e) { 
				Log.e(DEBUG, "End of stream", e);
				alertEndOfStream();
			} catch (Exception e) {
				Log.e(DEBUG, "End of stream", e);
			}
		}

	}

}
